# This package is to deal with photo backup relevant issues.

from pathlib import Path
from typing import Union
import os

from lwpackage.lwutils.logging import log
from lwpackage.lwutils.file_processor import move_file, make_path, clear_path, check_file_exists
from lwpackage.lwutils.lwpath import PREPROCESS_PHOTO_PATH, POSTPROCESS_PHOTO_PATH, FOLDER_TO_ALBUM_MAP


def move_photo_by_album(new_photo_name: str,
                        album: str,
                        photo_path: Union[str, Path],
                        postprocess_photo_path: Union[str, Path] = POSTPROCESS_PHOTO_PATH):
    """

    :param new_photo_name: new photo name of move-to-path
    :param album: album name, it is the folder of new photo
    :param photo_path: old photo path, that is, move-from photo path
    :param postprocess_photo_path:
    :return: True if overwrite photo or moved-to-photo does not exist.
    """
    move_to = os.path.join(postprocess_photo_path, album, new_photo_name)

    return move_file(photo_path, move_to, method='skip')


def processed_photo_name(photo_name: str):
    """
    Get processed photo name with specific logic.

    :param photo_name: preprocessed old photo name
    :return: processed_photo_name and belonged album
    """
    try:
        if photo_name.startswith('.'):
            photo_name = photo_name[1:]
        condition1 = photo_name.split('_')[0].isdigit() and len(photo_name.split('_')[0]) == 8
        condition2 = len(photo_name.split('_')) > 1 and photo_name.split('_')[1].isdigit() and len(
            photo_name.split('_')[1]) == 8
        if condition1:
            prefix = photo_name.split('.')[0]
            p1 = prefix.split('_')[0]
            p2 = prefix.split('_')[1][:6]
            prefix = '_'.join([p1, p2])
            suffix = photo_name.split('.')[-1]

            return '.'.join([prefix, suffix]), '相机'

        elif condition2:
            prefix = photo_name.split('.')[0]
            p1 = prefix.split('_')[1]
            p2 = prefix.split('_')[2][:6]
            prefix = '_'.join([p1, p2])
            suffix = photo_name.split('.')[-1]

            return '.'.join([prefix, suffix]), '相机'

        elif photo_name.startswith('mmexport') or photo_name.startswith('wx_camera'):
            return photo_name, '微信'
        elif photo_name.startswith('XHS_XHS') or photo_name.startswith('Camera_XHS'):
            return photo_name, '小红书'
        elif photo_name.split('.')[0].endswith('Bing'):
            return photo_name, 'bing'
        elif photo_name.startswith('Screenshot') or photo_name.startswith('SmartSelect'):
            return photo_name, '截屏'
        else:
            return photo_name, 'others'

    except Exception as e:
        print(f'Invalid format for {photo_name}, {e}.')


def _check_camera_photo_name(postprocess_photo_path: Union[str, Path] = POSTPROCESS_PHOTO_PATH):
    """
    Check whether photo names of Camera album are all valid, deleting invalid names.
    :param postprocess_photo_path:
    :return:
    """
    p = os.path.join(postprocess_photo_path, '相机')
    for photo in os.listdir(p):
        if not photo.startswith('.'):  # 读取到的一堆照片名称为诸如'._20230926_210639.jpg'这样的，可能是系统自带，不考虑这些。
            prefix = photo.split('.')[0]
            # specially deal with the photo prefix names end with (x)
            if prefix.endswith(')'):  # e.g.20231004_171155(1).jpg
                suffix = photo.split('.')[1]
                base_name = '.'.join([prefix[:-3], suffix])
                if check_file_exists(postprocess_photo_path, base_name):
                    os.remove(os.path.join(postprocess_photo_path, photo))
            else:
                e = f'{photo} in Camera folder with wrong type'
                assert prefix.split('_')[0].isdigit() and prefix.split('_')[1].isdigit(), e
                assert (len(prefix.split('_')[0]) == 8) and (len(prefix.split('_')[0]) == 8), e


def categorize_photos(preprocess_photo_path: Union[str, Path] = PREPROCESS_PHOTO_PATH,
                      postprocess_photo_path: Union[str, Path] = POSTPROCESS_PHOTO_PATH):
    """
    Categorize photos on preprocess_photo_path, and output processed photos into postprocess_photo_path. This process is
    based on the following special logic:

    照片的主要来源是三星手机。定期备份照片的目的就是将三星手机上新增的照片添加到硬盘中。为了实现这一目的，我设计了如下的方法：

    处理照片，基本目的：每次将Samsung Galaxy S23中的'/内部储存/DCIM/*'以及
    '/内部储存/Pictures/*'全部复制到移动硬盘的'/photos/整理前的照片'路径后，
    能够将整理好的照片输出到移动硬盘的'/photos/整理完成的照片'路径下，同时将整理前的照片路径清空。即：将整理前的照片路径作为一个缓存cache。

    实现步骤：
    1. 写一个在特定路径中增加指定照片的函数，如果一个路径中已经存在此照片就跳过，否则将此照片加入到该路径中
    2. 每次备份时将上述两个路径的所有文件都拷贝到硬盘中的整理前的照片路径，此路径中可能直接存在照片，以及各个类别的文件夹，
    每个类别的文件夹中存在照片。此处一些文件夹可能没有用，就直接拆开取出照片；另外的一些文件夹中的照片一定属于一个类别，
    就直接存入整理后的照片中的指定类别的文件夹中。（建议写一个判断指定路径下的文件类型是否为文件夹的方法，以及一个输出指定路径下的所有文件夹的方法）
    3. 写一个函数，判断每一张照片存放属于的类别，输入是照片的名称，输出是属于的类别，例如 'camera', 'weixin', 'xhs'等等。
    4. 在整理完成的照片路径中新建类别文件夹（如果不存在的话）
    5. 遍历每一张照片，将照片存放在整理后的照片目录中的指定类别的文件夹中，如果遇到无法归类的照片，输出log.warning，同时将照片归入others类别中
    6. 输出处理好的log，并且提示检查
    7. 检查完毕后运行另外一个函数，用于清空整理前的照片路径中的所有文件



    不同类型的照片处理准则如下：
    - 所有照片格式都尽量保存为年月日_时分秒.照片类型。
    - 相机拍摄的照片：统一按照日期命名，统一格式为 年月日_时分秒.照片类型，例如20231031_200206.jpg
    - 微信导出的照片（mmexport开头的是保存别人发的图片等的，wx_camera开头的是用微信的相机拍摄的照片，注意即使没有主动保存，
    微信相机拍摄的照片也会被保存）
    - 截屏：会被保存在Screenshot文件夹中。
    - bing搜索的照片：存放在bing文件夹中，或者是截屏，但是后缀是BING。
    - 软件的照片：存放在对应软件的子文件夹中，可以动态增加
        * Coolmarket：酷安
        * news_article：今日头条
        * bili: bilibili
        * 知乎: 知乎
        * WeiXin：微信
        * 小红书直接添加在DCIM路径下，且前缀为Camera_XHS
        * 其它类别（例如淘宝等）的照片不用特殊处理了，全部放在others中就好。



    在处理好之后，备份照片的流程得到了极大简化，每次只需要：
    1. 手机连接硬盘，将三星手机中的'/内部储存/DCIM/*'以及'/内部储存/Pictures/*'全部复制到移动硬盘的'/photos/整理前的照片'路径。如果提示
    已存在同名文件，就选择跳过。
    2. 运行代码，更新处理完成的照片路径
    3. 检查处理是否妥当，没问题后运行代码，清空整理前的照片路径中的所有文件。
    4. log输出处理的结果

    :param preprocess_photo_path: using as cache, default is PREPROCESS_PHOTO_PATH
    :param postprocess_photo_path: the output path on hard-disk, storing processed photos. The default is
        POSTPROCESS_PHOTO_PATH.
    :return: total processed photo number and newly-added photo number
    """
    total_photo_number, new_photo_number = 0, 0

    for album in FOLDER_TO_ALBUM_MAP.values():
        make_path(postprocess_photo_path, album)

    for root, folders, photos in os.walk(preprocess_photo_path):
        album_folder = os.path.basename(root)
        if album_folder in FOLDER_TO_ALBUM_MAP:
            for photo in photos:
                # 优先处理文件夹的逻辑
                # 如果photo_name和album_folder表示的类别不同且photo_name表示的类别不是others时，
                # 优先将照片归类到photo_name对应的类别。
                # 基本逻辑：Camera文件夹可能会包含其它名称的照片，我们将这类照片归类到photo_name表示的类别中，无论
                # photo_name表示的类别是否是others类。
                # 而其他文件夹例如Screenshots中虽然也会包含表示的类别不同的照片，但是这类照片一般就是属于本文件夹（Screenshots）。

                new_photo_name, album = processed_photo_name(photo_name=photo)

                if (FOLDER_TO_ALBUM_MAP[album_folder] != album) and (album_folder == 'Camera'):
                    new_album = album
                else:
                    new_album = FOLDER_TO_ALBUM_MAP[album_folder]

                if move_photo_by_album(new_photo_name=new_photo_name,
                                       album=new_album,
                                       photo_path=os.path.join(root, photo)):
                    new_photo_number += 1
                total_photo_number += 1
        else:
            for photo in photos:
                new_photo_name, new_album = processed_photo_name(photo_name=photo)
                if move_photo_by_album(new_photo_name=new_photo_name,
                                       album=new_album,
                                       photo_path=os.path.join(root, photo)):
                    new_photo_number += 1
                total_photo_number += 1

    log.info(f'Total number of processed photos is {total_photo_number}, '
             f'the number of newly-added photos is {new_photo_number}.')

    _check_camera_photo_name()

    clear_path(preprocess_photo_path)

    return total_photo_number, new_photo_number
